package de.reichelt.fx;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.controlsfx.control.decoration.Decorator;
import org.controlsfx.control.decoration.GraphicDecoration;

public class FxForm {
  
  private Logger log;
  private List<FxFormField> fields;
  private List<Button> buttons;
  private SimpleDateFormat fmt = new SimpleDateFormat("dd.MM.yyyy");
  private List<FxValidator> validators;
  
  public static int BUTTON_SAVE = 1;
  public static int BUTTON_CANCEL = 2;
  public static int BUTTON_CLOSE = 3;
  
  /**
   * erzeugt ein Datenformular.
   * 
   */
  public FxForm() {
    log = Logger.getLogger(this.getClass().getName());
    log.fine("erzeuge Formular");
    fmt.setLenient(false);
    this.fields = new ArrayList<>();
    this.validators = new ArrayList<>();
    this.buttons = new ArrayList<>();
  }
  
  public List<FxValidator> getValidators() {
    return validators;
  }

  public void addValidator(FxValidator validator) {
    this.validators.add(validator);
  }
  
  public List<FxFormField> getFields() {
    return fields;
  }
  
  public void registerButton(Button btn) {
    if (!fields.isEmpty()) {
      throw new RuntimeException("Bei reghisterButton() sind bereits Felder registriert!");
    }
    this.buttons.add(btn);
  }
  
  ChangeListener listener = new ChangeListener() {
    @Override
    public void changed(ObservableValue observable, Object oldValue, Object newValue) {
      for (Button btn: buttons) {
        btn.setDisable(false);
      }
    }
  };

  /**
   * registriert im Formular ein Control unter einem Namen und erzeugt optional 
   * einen FxNotEmptyValidator.
   * 
   * @param control  
   * @param name 
   * @param mandatory 
   * @param editable 
   */
  public FxFormField registerField(Control control, String name, boolean mandatory, 
          boolean editable) {
    FxFormField field = new FxFormField(control, name, mandatory, editable);
    this.fields.add(field);
    log.fine("addField() aufgerufen, name="+name);
    if (field.isMandatory()) {
      validators.add(new FxNotEmptyValidator(field));
      log.fine("FxNotEmptyValidator erzeugt.");
    }
    if (control instanceof TextInputControl) {
      ((TextInputControl)control).textProperty().addListener(listener);
    } else if (control instanceof ComboBox) {
      ((ComboBox)control).getSelectionModel().selectedItemProperty().addListener(listener);
    } else if (control instanceof DatePicker) {
      ((DatePicker)control).getEditor().textProperty().addListener(listener);
    } else if (control instanceof CheckBox) {
      ((CheckBox)control).selectedProperty().addListener(listener);
    }
    return field;
  }
  
  /**
   * registriert im Formular ein Control unter einem Namen und erzeugt optional 
   * einen FxNotEmptyValidator und/oder einen SubType-Validator.
   * 
   */
  public FxFormField registerField(Control control, String name, boolean mandatory, 
          boolean editable, FxFormField.SubType subType) {
    FxFormField field = registerField(control, name, mandatory, editable);
    field.setSubType(subType);
    if (subType==FxFormField.SubType.INTEGER) {
      validators.add(new FxIntegerValidator(field));
      log.fine("FxIntegerValidator erzeugt.");
    } else if (subType==FxFormField.SubType.DOUBLE) {
      validators.add(new FxDoubleValidator(field));
      log.fine("FxDoubleValidator erzeugt.");
    }
    return field;
  }
  
  public FxFormField getField(String fieldName) {
    for (FxFormField f: fields) {
      if (f.getName().equalsIgnoreCase(fieldName)) return f;
    }
    return null;
  }
  
  public void clear() {
    for (FxFormField f: fields) {
      if (f.getControl() instanceof TextInputControl) {
        ((TextInputControl)f.getControl()).clear();
      } else if (f.getControl() instanceof ComboBox) {
        ((ComboBox)f.getControl()).setValue(null);
      } else if (f.getControl() instanceof DatePicker) {
        ((DatePicker)f.getControl()).getEditor().setText(null);
      } else if (f.getControl() instanceof CheckBox) {
        ((CheckBox)f.getControl()).setSelected(false);
      }
    }
  }

  public void disable() {
    for (FxFormField f: fields) {
      if (f.getControl() instanceof DatePicker) {
        ((DatePicker)f.getControl()).setDisable(true);
      } else if (f.getControl() instanceof TextInputControl) {
        ((TextInputControl)f.getControl()).setEditable(false);
        f.getControl().setFocusTraversable(false);
      } else if (f.getControl() instanceof ComboBox) {
        ((ComboBox)f.getControl()).getEditor().setEditable(false);
        ((ComboBox)f.getControl()).getEditor().setFocusTraversable(false);
      } else if (f.getControl() instanceof Button) {
        ((Button)f.getControl()).setDisable(true);
      }
    }
  }
  
  public void enable() {
    for (FxFormField f: fields) {
      if (f.getControl() instanceof DatePicker) {
        ((DatePicker)f.getControl()).setDisable(false);
      } else if (f.getControl() instanceof TextInputControl) {
        ((TextInputControl)f.getControl()).setEditable(true);
        f.getControl().setFocusTraversable(true);
      } else if (f.getControl() instanceof ComboBox) {
        ((ComboBox)f.getControl()).getEditor().setEditable(true);
        ((ComboBox)f.getControl()).getEditor().setFocusTraversable(true);
      } else if (f.getControl() instanceof Button) {
        ((Button)f.getControl()).setDisable(false);
      }
    }
  }
  
  /**
   * validiert die Daten des Formulars und gibt die Fehler als Liste zurück.
   * 
   * @return Fehlerliste
   * @throws Exception 
   */
  public boolean validate() throws Exception {
    boolean valid = true;
    for (FxFormField fld: fields) {
      for (FxValidator val: fld.getValidators()) {
        if (!val.isValid()) {
          showValidationError(fld.getControl(), val.getMessage());
          log.fine("Datenfehler für "+val.getClass().getSimpleName()+" von "+val.field.getName());
          valid=false;
        }
      }
    }
    for (FxValidator val: this.getValidators()) {
      if (!val.isValid()) {
        showValidationError(val.field.getControl(), val.getMessage());
        log.fine("Datenfehler für "+val.getClass().getSimpleName()+" von "+val.field.getName());
        valid=false;
      }
    }
    return valid;
  }

  public void showValidationError(Control ctl, String message) {
    //fehlerhafte Felder mit Symbol markieren
    Image png = new Image(this.getClass().getResourceAsStream("alert.png"));
    ImageView x = new ImageView(png);
    x.setFitWidth(16);
    x.setPreserveRatio(true);
    x.setSmooth(true);
    Tooltip t = new Tooltip(message);
    Tooltip.install(x, t);
    Decorator.addDecoration(ctl, new GraphicDecoration(x, Pos.TOP_LEFT, -5, +5));
  }

  public void resetErrors() {
    for (FxFormField fld: fields) {
      Decorator.removeAllDecorations(fld.getControl());
    }
  }
  
}
